#Requires AutoHotkey v2.0
#SingleInstance force

; Nice Project:
;   + https://biga-ahk.github.io/biga.ahk/#/
;   + https://github.com/TheArkive/JXON_ahk2
; Document:

class XUtils {
    ; ################ is* function ################
    /**
     * 判断是否为字符串
     * @param {Any} value 需要判断的对象
     * @returns {Boolean} 判断结果
     */
    isString(value) {
        if (type(value) = "String") {
            return true
        }
        return false
    }

    /**
     * 判断是否为数值型数据，注意数字字符串会判定为不是数值型数据
     * @param {Any} value 需要判断的对象
     * @returns {Boolean} 判断结果
     */
    isNumber(value) {
        if (!this.isString(value) and isNumber(value)) {
            return true
        }
        return false
    }

    /**
     * 判断是否为整型数据，注意数字字符串会判定为不是数值型数据
     * @param {Any} value 需要判断的对象
     * @returns {Boolean} 判断结果
     */
    isInteger(value) {
        if (!this.isString(value) and isInteger(value)) {
            return true
        }
        return false
    }

    /**
     * 判断是否为浮点型数据，注意数字字符串会判定为不是数值型数据
     * @param {Any} value 需要判断的对象
     * @returns {Boolean} 判断结果
     */
    isFloat(value) {
        if (!this.isString(value) and isFloat(value)) {
            return true
        }
        return false
    }

    /**
     * 判断是否为列表
     * @param {Any} value 需要判断的对象
     * @returns {Boolean} 判断结果
     */
    isArray(value) {
        if (type(value) = "Array") {
            return true
        }
        return false
    }

    /**
     * 判断是否为 MAP 表
     * @param {Any} value 需要判断的对象
     * @returns {Boolean} 判断结果
     */
    isMap(value) {
        if (type(value) = "Map") {
            return true
        }
        return false
    }

    /**
     * 判断是否设置值
     * @param {Any} value 需要判断的对象
     * @returns {Boolean} 判断结果
     */
    isSet(value) {
        return isSet(value)
    }

    /**
     * 判断是否为对象
     * @param {Any} value 需要判断的对象
     * @returns {Boolean} 判断结果
     */
    isObject(value) {
        return isObject(value)
    }

    /**
     * 判断是否为 COM 对象
     * @param {Any} value 需要判断的对象
     * @returns {Boolean} 判断结果
     */
    isComObject(value) {
        return this.isInteger(comObjType(value))
    }

    ; ################ number function ################
    /**
     * 返回指定数字四舍五入到 N 位小数的数字
     * @param {Number} num 任意数值类型变量
     * @returns {String} 转化后的字符串
     */
    round(num, n := 0) {
        return round(num, n)
    }

    /**
     * 将任意进制的字符串转为10进制的值
     * @param {String} value 任意进制的数值字符串，如“1A8D”
     * @param {Integer} base 数值字符串对应的进制
     * @returns {Integer} 对应的10进制数值
     */
    fromBase(value, base := 16) {
        value := strUpper(value)
        ret := 0
        power := 0
        while (true) {
            last_str_number := subStr(value, -1)
            if (this.len(last_str_number) < 1) {
                break
            }
            last_number := ord(last_str_number)
            if (last_number >= 48 and last_number <= 57) {
                last_number -= 48
            } else if (last_number >= 65 and last_number <= 90) {
                last_number -= 55
            } else {
                throw ValueError(this.format("数值 {} 超出预期", last_str_number))
            }
            ret += base ** power * last_number
            value := subStr(value, 1, this.len(value) - 1)
            power += 1
        }
        return ret
    }

    /**
     * 将10进制的数值转为任意进制的字符串
     * @param {Integer} value 10进制数值
     * @param {Integer} base 数值字符串对应的进制
     * @returns {String} 任意进制的数值字符串
     */
    toBase(value, base := 16) {
        ret := ""
        while (true) {
            num := mod(value, base)
            if (num > 10) {
                ret := chr(num + 55) . ret
            } else {
                ret := num . ret
            }
            value := value // base
            if (value < base) {
                if (value > 0) {
                    if (value > 10) {
                        ret := chr(value + 55) . ret
                    } else {
                        ret := value . ret
                    }
                }
                break
            }
        }
        return ret
    }

    ; ################ string function ################
    /**
     * 将任意类型对象转为字符串输出，如：传入 map("11", 22) 会输出 {'11': 22}
     * @param {Any} obj 任意类型变量
     * @returns {String} 转化后的字符串
     */
    toString(obj) {
        output := ""
        if (this.isNumber(obj)) {
            return format("{}", obj)
        } else if (this.isString(obj)) {
            return format("'{}'", obj)
        } else if (this.isMap(obj) or this.isArray(obj)) {
            is_map := this.isMap(obj)
            for key, value in obj {
                if (is_map) {
                    output .= format("{}: ", this.toString(key))
                }
                if (this.isObject(value)) {
                    output .= this.toString(value)
                } else {
                    output .= this.toString(value)
                }
                output .= ", "
            }
            output := subStr(output, 1, strlen(output) - 2)
            if (is_map) {
                return "{" . output . "}"
            }
            return format("[{}]", output)
        } else if (this.isObject(obj) and !comObjType(obj)) {
            ; source: https://www.autohotkey.com/boards/viewtopic.php?t=111713
            for key, value in obj.ownProps() {
                output .= key "=" this.toString(value) . ", "
            }
            if hasMethod(obj, "__Enum") {
                output .= "Enums: "
                for key, value in obj.__Enum() {
                    output .= key "=" this.toString(value) . ", "
                }
            }
            output := subStr(output, 1, strlen(output) - 2)
            return format("<Object '{}', {}>", type(obj), output)
        }
        return format("<Class '{}'>", type(obj))
    }

    ; ################ array function ################
    /**
     * 搜索数组，检查是否存在指定元素，如果存在则返回对应元素的索引
     * @param {Array} arr_any 待搜索的数组
     * @param {Any} elm 搜索的目标元素
     * @param {Number} default 如果找不到目标元素，则返回默认值，默认值一般为 -1
     * @returns {Number} 
     */
    indexOf(arr_any, elm, default := -1) {
        for index, value in arr_any {
            if (elm = value) {
                return index
            }
        }
        return default
    }

    /**
     * 将多个字符串，使用指定的 separator 进行拼接
     * @param {String} arr_str 待拼接的字符串
     * @param {String} separator 拼接使用的分隔符
     * @returns {String} 拼接后的字符串
     */
    join(arr_str, separator := " ") {
        output := ""
        for _, value in arr_str {
            if (this.isObject(value)) {
                value := this.toString(value)
            }
            output .= value . separator
        }
        return substr(output, 1, strlen(output) - strlen(separator))
    }

    /**
     * 根据格式字符串格式化后返回数据，支持对 Object 对象转为字符串
     * @param {String} string_format 格式字符串由原义文本和占位符组成，其形式为 {Index:Format}
     * @param {Any} params 要格式化的输入值
     * @returns {String} 指定字符串的格式化版本
     */
    format(string_format, params*) {
        for index, param in params {
            if (this.IsObject(param)) {
                params[index] := this.toString(param)
            }
        }
        return format(string_format, params*)
    }

    ; ################ random function ################
    /**
     * 生成随机数
     * @param {Float | Integer} min_num 随机数最小值
     * @param {Float | Integer} max_num 随机数最大值
     * @returns {Float | Integer} 
     */
    rnd(min_num := 0, max_num := 1.0) {
        return random(min_num, max_num)
    }

    /**
     * 随机选择数组中的任意元素并返回
     * @param {Any} arr_any 任意类型元素，如果直接传入的是数组，需要在数组变量名后加*号
     */
    rnd_choice(arr_any*) {
        return arr_any[this.rnd(1, this.len(arr_any))]
    }

    /**
     * 随机选择数组中的任意样本并返回，不会选择同样的元素作为样本
     * @param {Any} arr_any 任意类型数组
     */
    rnd_sample(arr_any, num := -1) {
        ret := []
        t_array := arr_any.clone()
        if num < 0 {
            num := this.len(t_array)
        }
        while num {
            index := this.rnd(1, this.len(t_array))
            ret.push(t_array[index])
            t_array.removeAt(index)
            num -= 1
        }
        return ret
    }

    ; ################ utils function ################
    /**
     * 判断任意遍历的长度，字符串型直接计算字符个数，整数型计算数字个数，浮点型去掉尾部0后计算数字个数，对象类型会调用 count() 函数计算元素个数；
     * @param {Object|String|Integer} value 
     * @returns {Integer} 
     */
    len(value) {
        if (this.isArray(value)) {
            return value.length
        } else if (this.isMap(value)) {
            return value.count
        } else if (this.isInteger(value) or this.isString(value)) {
            return strlen(value)
        }
        ; else if (this.isFloat(value)) {
        ;     return strlen(rtrim(value, "0")) - 1
        ; }
        throw TypeError(this.format("type '{}' not supported", type(value)), -1)
        ; return strlen(value)
    }

    /**
     * 在控制台中打印任意数据，如果不是控制台启动会出现 "Error: (6) The handle is invalid." 函数本身会直接跳过执行
     * @param params 
     */
    print(params*) {
        ; TODO: When "Reload" program, the "Error: (6) The handle is invalid." There is no solution
        try {
            fileAppend(this.join(params) '`n', "*")
        } catch error {
            return false
        }
        return true
    }

    /**
     * 
     * @param text 
     */
    msgbox(text, title := A_ScriptName, opts := 0) {
        if (this.isObject(text)) {
            text := this.toString(text)
        }
        msgbox(text, title, opts)
    }

    /**
     * 获取当前时间的时间戳，默认以微秒返回时间戳
     * @param {Boolean} is_return_seconds 如果为 true 则以秒为单位返回时间戳；否则返回以微秒为单位返回时间戳
     * @returns {Number} 时间戳数值
     */
    now(is_return_seconds := false) {
        power_num := 1
        if (is_return_seconds) {
            power_num := 1000
        }
        return this.round((dateDiff(A_NowUTC, 19700101000000, "seconds") * 1000 + A_MSec) / power_num, 3)
    }

    ; Source: https://github.com/TheArkive/JXON_ahk2
    jsonDump(obj) {
    }

    ; Source: https://github.com/TheArkive/JXON_ahk2
    jsonLoad(json_string) {
    }

    ; ################ regex function ################
    /**
     * 正则匹配默认会匹配完字符串后再返回数据，或者匹配到 count 的数量后就返回数据
     * @param {String} raw_string 待搜索匹配字符串
     * @param {String} reg_string 正则字符串
     * @param {Number} count 匹配次数，达到指定次数后函数会直接返回
     * @returns {Array} 所有匹配结果列表，列表内为 RegExMatchInfo 对象
     */
    findall(raw_string, reg_string, count := -1) {
        ret := []
        s_pos := 1
        e_pos := 1
        match_info := unset
        while (count != 0) {
            e_pos := regexMatch(raw_string, reg_string, &match_info, s_pos)
            if !e_pos {
                break
            }
            ret.push(match_info)
            count -= 1
            s_pos := e_pos + 1
        }
        return ret
    }

    ; 正则匹配并替换，如果 only_sub 为 true 则只对匹配到的子字符串进行替换，否则会替换匹配到所有内容
    ; replace(raw_string, reg_string, only_sub := false, count := -1) {
    ;     ret := []
    ;     s_pos := 1
    ;     match_info := unset
    ;     while (e_pos := regExMatch(raw_string, reg_string, &match_info, s_pos))
    ;     {
    ;         ret.push(match_info)
    ;         count -= 1
    ;         s_pos += e_pos
    ;     }
    ;     return ret
    ; }
}

class XFile {
    static XU := XUtils()

    exist(file_pattern, attr := "") {
        if attr {
            return InStr(FileExist(file_pattern), attr)
        }
        return FileExist(file_pattern)
    }

    is_dirt(file_pattern) {
        return this.exist(file_pattern, "D")
    }

    is_file(file_pattern) {
        return this.exist(file_pattern, "A")
    }
}